import { render } from '@testing-library/react';
import { mount, shallow } from 'enzyme';
import React from 'react';
import renderer from 'react-test-renderer';
import styled, { css, ThemeProvider } from 'styled-components';
import '../src';

const notToHaveStyleRule = (component, property, value) => {
  expect(renderer.create(component).toJSON()).not.toHaveStyleRule(property, value);
  expect(shallow(component)).not.toHaveStyleRule(property, value);
  expect(mount(component)).not.toHaveStyleRule(property, value);
  expect(render(component).container.firstChild).not.toHaveStyleRule(property, value);
};

const toHaveStyleRule = (component, property, value, options) => {
  expect(renderer.create(component).toJSON()).toHaveStyleRule(property, value, options);
  expect(shallow(component)).toHaveStyleRule(property, value, options);
  expect(mount(component)).toHaveStyleRule(property, value, options);
  expect(render(component).container.firstChild).toHaveStyleRule(property, value, options);
};

it('null', () => {
  expect(null).not.toHaveStyleRule('a', 'b');
});

it('non-styled', () => {
  notToHaveStyleRule(<div />, 'a', 'b');
});

it('message when rules not found', () => {
  expect(() =>
    expect(renderer.create(<div />).toJSON()).toHaveStyleRule('color', 'black')
  ).toThrowErrorMatchingSnapshot();
});

it('message when rules not found using options', () => {
  const Button = styled.button`
    color: red;
  `;

  toHaveStyleRule(<Button />, 'color', 'red');
  expect(() =>
    expect(renderer.create(<Button />).toJSON()).toHaveStyleRule('color', 'red', {
      media: '(max-width:640px)',
      modifier: ':hover',
    })
  ).toThrowErrorMatchingSnapshot();
});

it('message when property not found', () => {
  const Button = styled.button`
    color: red;
  `;

  expect(() =>
    expect(renderer.create(<Button />).toJSON()).toHaveStyleRule('background-color', 'black')
  ).toThrowErrorMatchingSnapshot();
});

it('message when value does not match', () => {
  const Wrapper = styled.section`
    background: orange;
  `;

  expect(() => {
    expect(renderer.create(<Wrapper />).toJSON()).toHaveStyleRule('background', 'red');
  }).toThrowErrorMatchingSnapshot();
});

it('non existing', () => {
  const Wrapper = styled.section`
    padding: 4em;
    background: papayawhip;
  `;

  expect(() => {
    expect(shallow(<Wrapper />).find('div')).toHaveStyleRule('background', 'papayawhip');
  }).toThrowErrorMatchingSnapshot();
});

it('basic', () => {
  const Wrapper = styled.section`
    padding: 4em;
    background: papayawhip;
  `;

  toHaveStyleRule(<Wrapper />, 'background', 'papayawhip');
});

it('regex', () => {
  const Wrapper = styled.section`
    padding: 4em;
    background: papayawhip;
  `;

  toHaveStyleRule(<Wrapper />, 'background', /^p/);
});

it('complex string', () => {
  const Wrapper = styled.section`
    border: 1px solid rgba(0, 0, 0, 0.125);
  `;

  toHaveStyleRule(<Wrapper />, 'border', '1px solid rgba(0,0,0,0.125)');
});

it('undefined', () => {
  const Button = styled.button`
    cursor: ${({ disabled }) => !disabled && 'pointer'};
    opacity: ${({ disabled }) => disabled && '.65'};
  `;

  toHaveStyleRule(<Button />, 'opacity', undefined);
  toHaveStyleRule(<Button />, 'cursor', 'pointer');
  toHaveStyleRule(<Button disabled />, 'opacity', '.65');
  toHaveStyleRule(<Button disabled />, 'cursor', undefined);
});

it('negated ".not" modifier with no value', () => {
  const Button = styled.button`
    opacity: ${({ disabled }) => disabled && '.65'};
  `;

  notToHaveStyleRule(<Button />, 'opacity');
  toHaveStyleRule(<Button disabled />, 'opacity', '.65');
});

it('negated ".not" modifier with value', () => {
  const Button = styled.button`
    opacity: 0.65;
  `;

  notToHaveStyleRule(<Button />, "opacity", "0.50");
  notToHaveStyleRule(<Button />, "opacity", "");
  notToHaveStyleRule(<Button />, "opacity", null);
  notToHaveStyleRule(<Button />, "opacity", false);
  expect(() => {
    toHaveStyleRule(<Button />, "opacity", undefined);
  }).toThrowError();
});

it('negated ".not" modifier fails when rule present with no value being asserted', () => {
  const Button = styled.button`
    opacity: 0.65;
  `;

  toHaveStyleRule(<Button />, "opacity", "0.65");
  expect(() => {
    notToHaveStyleRule(<Button />, "opacity");
  }).toThrowError();
});

it('jest asymmetric matchers', () => {
  const Button = styled.button`
    border: 0.1em solid ${({ transparent }) => (transparent ? 'transparent' : 'black')};
  `;

  toHaveStyleRule(<Button />, 'border', expect.any(String));
  toHaveStyleRule(<Button />, 'border', expect.stringMatching('solid'));
  toHaveStyleRule(<Button />, 'border', expect.stringMatching(/^0.1em/));
  toHaveStyleRule(<Button />, 'border', expect.stringContaining('black'));
  notToHaveStyleRule(<Button transparent />, 'border', expect.stringContaining('black'));
  toHaveStyleRule(<Button transparent />, 'border', expect.stringContaining('transparent'));
  notToHaveStyleRule(<Button />, 'color', expect.any(String));
  notToHaveStyleRule(<Button />, 'color', expect.anything());
});

it('any component', () => {
  const Link = ({ className, children }) => <a className={className}>{children}</a>;

  const StyledLink = styled(Link)`
    color: palevioletred;
    font-weight: bold;
  `;

  toHaveStyleRule(<StyledLink>Styled, exciting Link</StyledLink>, 'color', 'palevioletred');
});

it('styled child', () => {
  const Parent = styled.div`
    color: red;
  `;

  const StyledChild = styled(Parent)`
    padding: 0;
  `;

  toHaveStyleRule(<StyledChild />, 'color', 'red');
});

it('theming', () => {
  const Button = styled.button`
    font-size: 1em;
    margin: 1em;
    padding: 0.25em 1em;
    border-radius: 3px;

    color: ${(props) => props.theme.main};
    border: 2px solid ${(props) => props.theme.main};
  `;

  Button.defaultProps = {
    theme: {
      main: 'palevioletred',
    },
  };

  const theme = {
    main: 'mediumseagreen',
  };

  toHaveStyleRule(<Button>Normal</Button>, 'color', 'palevioletred');

  const component = (
    <ThemeProvider theme={theme}>
      <Button>Themed</Button>
    </ThemeProvider>
  );

  expect(renderer.create(component).toJSON()).toHaveStyleRule('color', 'mediumseagreen');
  expect(mount(component)).toHaveStyleRule('color', 'mediumseagreen');
});

it('at rules', () => {
  const Wrapper = styled.section`
    color: red;
    @media (max-width: 640px) {
      color: green;
    }
    @media (min-width: 200px) and (max-width: 640px) {
      color: blue;
    }
    @media (min-width: 576px) and (max-width: 767.98px) {
      color: red;
    }
    @media (min-width: calc(768px + 1px)) and (max-width:calc(1024px + 1px)) {
      color: purple;
    }
  `;

  toHaveStyleRule(<Wrapper />, 'color', 'red');
  toHaveStyleRule(<Wrapper />, 'color', 'green', {
    media: '(max-width:640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'green', {
    media: '(max-width: 640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'blue', {
    media: '(min-width:200px) and (max-width:640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'blue', {
    media: '(min-width: 200px) and (max-width: 640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'blue', {
    media: '(min-width: 200px) and (max-width:640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'blue', {
    media: '(min-width:200px) and (max-width: 640px)',
  });
  toHaveStyleRule(<Wrapper />, 'color', 'red', {
    media: '(min-width: 576px) and (max-width: 767.98px)',
  });
  toHaveStyleRule(<Wrapper />, "color", "purple", {
    media: "(min-width: calc(768px + 1px)) and (max-width:calc(1024px + 1px))"
  });
});

it('selector modifiers', () => {
  const Link = styled.a`
    color: white;

    &:hover {
      color: blue;
    }
    &::after {
      color: red;
    }
    &[href*='somelink.com'] {
      color: green;
    }
    > div {
      color: yellow;
    }
    span {
      color: purple;
    }
    .child {
      color: orange;
    }
    &.self {
      color: black;
    }

    .one,
    .two {
      color: olive;
    }

    ~ div {
      &.one,
      &.two {
        color: pink;
      }
    }

    + div {
      .one,
      .two {
        color: salmon;
      }
    }

    .parent & {
      color: red;
    }

    && {
      color: fuchsia;
    }

    &&& {
      color: olive;
    }

    & & {
      color: deepskyblue;
    }
  `;

  toHaveStyleRule(<Link />, 'color', 'white');
  toHaveStyleRule(<Link />, 'color', 'blue', {
    modifier: ':hover',
  });
  toHaveStyleRule(<Link />, 'color', 'red', {
    modifier: '::after',
  });
  toHaveStyleRule(<Link />, 'color', 'green', {
    modifier: "[href*='somelink.com']",
  });
  toHaveStyleRule(<Link />, 'color', 'yellow', {
    modifier: '> div',
  });
  toHaveStyleRule(<Link />, 'color', 'purple', {
    modifier: 'span',
  });
  toHaveStyleRule(<Link />, 'color', 'purple', {
    modifier: ' span',
  });
  toHaveStyleRule(<Link />, 'color', 'orange', {
    modifier: '.child',
  });
  toHaveStyleRule(<Link />, 'color', 'orange', {
    modifier: ' .child',
  });
  toHaveStyleRule(<Link />, 'color', 'black', {
    modifier: '&.self',
  });
  toHaveStyleRule(<Link />, 'color', 'olive', {
    modifier: '.one',
  });
  toHaveStyleRule(<Link />, 'color', 'olive', {
    modifier: '.two',
  });
  toHaveStyleRule(<Link />, 'color', 'pink', {
    modifier: '~ div.one',
  });
  toHaveStyleRule(<Link />, 'color', 'salmon', {
    modifier: '+ div .two',
  });
  toHaveStyleRule(<Link />, 'color', 'red', {
    modifier: '.parent &',
  });
  toHaveStyleRule(<Link />, 'color', 'fuchsia', {
    modifier: '&&',
  });
  toHaveStyleRule(<Link />, 'color', 'olive', {
    modifier: '&&&',
  });
  toHaveStyleRule(<Link />, 'color', 'deepskyblue', {
    modifier: '& &',
  });
});

it('component modifiers', () => {
  const Text = styled.span`
    color: grey;
  `;

  const Link = styled.a`
    color: white;

    ${Text} {
      color: blue;
    }

    > ${Text} span {
      color: green;
    }

    ${Text} & {
      color: purple;
    }
  `;

  toHaveStyleRule(<Link />, 'color', 'white');
  toHaveStyleRule(<Text />, 'color', 'grey');
  toHaveStyleRule(
    <Link>
      <Text />
    </Link>,
    'color',
    'blue',
    {
      modifier: css`${Text}`
    }
  );
  toHaveStyleRule(
    <Link>
      <Text />
    </Link>,
    'color',
    'green',
    {
      modifier: css`> ${Text} span`,
    }
  );
  toHaveStyleRule(
    <Link>
      <Text />
    </Link>,
    'color',
    'purple',
    {
      modifier: css`${Text} &`
    }
  );
});

it('nested', () => {
  const Wrapper = styled.section`
    padding: 4em;
    background: papayawhip;
  `;

  const MyComponent = () => <Wrapper />;

  toHaveStyleRule(<MyComponent />, 'background', 'papayawhip');
});

it('nested with styling', () => {
  const Wrapper = styled.section`
    background: papayawhip;
  `;
  const Children = styled.span`
    background: gray;
  `;
  const MyComponent = (props) => <Wrapper {...props} />;
  const MyStyledComponent = styled(MyComponent)`
    color: red;
  `;
  const ParentComponent = (props) => (
    <MyStyledComponent {...props}>
      <Children className="test-class" />
    </MyStyledComponent>
  );

  toHaveStyleRule(<MyStyledComponent />, 'color', 'red');
  toHaveStyleRule(<MyStyledComponent className="test-class" />, 'color', 'red');
  expect(shallow(<ParentComponent />).find(Children)).toHaveStyleRule('background', 'gray');
  expect(mount(<ParentComponent />).find(Children)).toHaveStyleRule('background', 'gray');
});

it('empty children', () => {
  const Wrapper = styled.section`
    padding: 4em;
    background: papayawhip;
  `;

  Wrapper.defaultProps = {
    children: '',
  };

  toHaveStyleRule(<Wrapper />, 'background', 'papayawhip');
});

it('custom display name prefix', () => {
  const Text = styled.span.withConfig({ displayName: 'Text__sc' })`
    color: red;
  `;
  const Comp = styled(Text).withConfig({ displayName: 'Comp__Sub-sc' })`
    background: papayawhip;
  `;
  toHaveStyleRule(<Comp />, 'background', 'papayawhip');
  toHaveStyleRule(<Comp />, 'color', 'red');
});

it("supports snake case display name prefix", () => {
  const Text = styled.span`
    color: blue;
  `;
  Text.styledComponentId = `test-case-${Text.styledComponentId}`;

  toHaveStyleRule(<Text />, "color", "blue");
});
